package math;

import java.awt.Color;
import java.util.Scanner;


/**
 * 3D vector class that contains three doubles. Could be used to represent
 * Vectors but also Points and Colors.
 * 
 */
public class Vec {

	protected static final int MINUS_ONE = -1;
	protected static final int ZERO = 0;
	private static final int SQUARE = 2;

	/**
	 * Vector data. Allowed to be accessed publicly for performance reasons
	 */
	public double x, y, z;

	/**
	 * Initialize vector to (0,0,0)
	 */
	public Vec() {
		this.x = ZERO;
		this.y = ZERO;
		this.z = ZERO; 
	}

	/**
	 * Initialize vector to given coordinates
	 * 
	 * @param x
	 *            Scalar
	 * @param y
	 *            Scalar
	 * @param z
	 *            Scalar
	 */
	public Vec(double x, double y, double z) {
		this.x = x;
		this.y = y;
		this.z = z;
	}

	/**
	 * Initialize vector values to given vector (copy by value)
	 * 
	 * @param v
	 *            Vector
	 */
	public Vec(Vec v) {
		this.x = v.x;
		this.y = v.y;
		this.z = v.z;
	}
	
	/**
	 * Creates a vector out of scanned attributes
	 * @param writtenCords
	 */
	public Vec(String writtenCords) {
		Scanner scan = new Scanner(writtenCords);
		this.x = scan.nextDouble();
		this.y = scan.nextDouble();
		this.z = scan.nextDouble();
	}
	
	/**
	 * Create a vector from 3D point coordinates
	 * @param p1
	 */
	public Vec(Point3D p1) {
		this.x = p1.x;
		this.y = p1.y;
		this.z = p1.z;
	}
	
	/**
	 * Create a vector that connects two points
	 * @param p
	 * @param center
	 */
	public Vec(Point3D p1, Point3D p2) {
		this.x = p2.x - p1.x;
		this.y = p2.y - p1.y;
		this.z = p2.z - p1.z;
	}

	/**
	 * Calculates the reflection of the vector in relation to a given surface
	 * normal. The vector points at the surface and the result points away.
	 * 
	 * @return The reflected vector
	 */
	public Vec reflect(Vec normal) {
		double g = normal.dotProd(this);
		Vec r = clone();
		r.add(Vec.scale(-2 * g, normal));
		return r;
	}

	/**
	 * Adds a to vector
	 * 
	 * @param a
	 *            Vector
	 */
	public void add(Vec a) {
		this.x = x + a.x;
		this.y = y + a.y;
		this.z = z + a.z;
	}
	
	/**
	 * Addition between 3D Point and a vector
	 * @param p1
	 * @param v1
	 * @return, the new vector
	 */
	public Vec addPointToVec(Point3D p1, Vec v1) {
		return new Vec(p1.x + v1.x,
						p1.y + v1.y,
						p1.z + v1.z);
	}

	/**
	 * Subtracts from vector
	 * 
	 * @param a
	 *            Vector
	 */
	public void sub(Vec a) {
		this.x = x - a.x;
		this.y = y - a.y;
		this.z = z - a.z;
	}
	
	/**
	 * Multiplies & Accumulates vector with given vector and a. v := v + s*a
	 * 
	 * @param s
	 *            Scalar
	 * @param a
	 *            Vector
	 */
	public void mac(double s, Vec a) {
		x += s * a.x;
		y += s * a.y;
		z += s * a.z;
	}

	/**
	 * Multiplies vector with scalar. v := s*v
	 * 
	 * @param s
	 *            Scalar
	 */
	public void scale(double s) {
		x *= s;
		y *= s;
		z *= s;
	}

	/**
	 * Pairwise multiplies with another vector
	 * 
	 * @param a
	 *            Vector
	 */
	public void scale(Vec a) {
		x *= a.x;
		y *= a.y;
		z *= a.z;
	}

	/**
	 * Inverses vector
	 * 
	 * @return Vector
	 */
	public void negate() {
		x = -x;
		y = -y;
		z = -z;
	}

	/**
	 * Computes the vector's magnitude
	 * 
	 * @return Scalar
	 */
	public double length() {
		return Math.sqrt(this.x * this.x + this.y *this.y + this.z * this.z);
	}

	/**
	 * Computes the vector's magnitude squared. Used for performance gain.
	 * 
	 * @return Scalar
	 */
	public double lengthSquared() {
		return Math.pow(this.length(), SQUARE);		
	}

	/**
	 * Computes the dot product between two vectors
	 * 
	 * @param a
	 *            Vector
	 * @return Scalar
	 */
	public double dotProd(Vec a) {
		return (this.x * a.x + this.y * a.y + this.z * a.z);
	}

	/**
	 * Normalizes the vector to have length 1. Throws exception if magnitude is zero.
	 * 
	 * @throws ArithmeticException
	 */
	public void normalize() throws ArithmeticException {
		double length = this.length();

		this.x /= length;
		this.y /= length;
		this.z /= length;
	}

	/**
	 * Compares to a given vector
	 * 
	 * @param a
	 *            Vector
	 * @return True if have same values, false otherwise
	 */
	public boolean equals(Vec a) {
		return ((a.x == x) && (a.y == y) && (a.z == z));
	}

	/**
	 * Returns the angle in radians between this vector and the vector
	 * parameter; the return value is constrained to the range [0,PI].
	 * 
	 * @param v1
	 *            the other vector
	 * @return the angle in radians in the range [0,PI]
	 */
	public final double angle(Vec v1) {
		Vec thisNormalized = new Vec(this);
		Vec vNormalized = new Vec(v1);
		
		// Normalizes the two vectors
		thisNormalized.normalize();
		vNormalized.normalize();

		// returns the acos of the two normlized vector
		return Math.acos(thisNormalized.dotProd(vNormalized));
	}

	/**
	 * Computes the cross product between two vectors using the right hand rule
	 * 
	 * @param a
	 *            Vector1
	 * @param b
	 *            Vector2
	 * @return Vector1 x Vector2
	 */
	public static Vec crossProd(Vec a, Vec b) {	
		return new Vec((a.y * b.z - a.z * b.y), 
				       (a.z * b.x - a.x * b.z), 
				       (a.x * b.y - a.y * b.x));
	}

	/**
	 * Adds vectors a and b
	 * 
	 * @param a
	 *            Vector
	 * @param b
	 *            Vector
	 * @return a+b
	 */
	public static Vec add(Vec a, Vec b) {
		return new Vec(a.x + b.x,
				       a.y + b.y,
				       a.z + b.z);
	}

	/**
	 * Subtracts vector b from a
	 * 
	 * @param a
	 *            Vector
	 * @param b
	 *            Vector
	 * @return a-b
	 */
	public static Vec sub(Vec a, Vec b) {
		return new Vec(a.x - b.x,
			       	   a.y - b.y,
			           a.z - b.z);
	}

	/**
	 * Inverses vector's direction
	 * 
	 * @param a
	 *            Vector
	 * @return -1*a
	 */
	public static Vec negate(Vec a) {
		Vec vector = a.clone();
		
		vector.negate();
		return vector;
	}

	/**
	 * Scales vector a by scalar s
	 * 
	 * @param s
	 *            Scalar
	 * @param a
	 *            Vector
	 * @return s*a
	 */
	public static Vec scale(double s, Vec a) {
		return new Vec(a.x * s, a.y * s, a.z * s);
	}

	/**
	 * Pair-wise scales vector a by vector b
	 * 
	 * @param a
	 *            Vector
	 * @param b
	 *            Vector
	 * @return a.*b
	 */
	public static Vec scale(Vec a, Vec b) {
		return new Vec(a.x * b.x, a.y * b.y, a.z * b.z);
	}

	/**
	 * Compares vector a to vector b
	 * 
	 * @param a
	 *            Vector
	 * @param b
	 *            Vector
	 * @return a==b
	 */
	public static boolean equals(Vec a, Vec b) {
		return a.equals(b);
	}

	/**
	 * Dot product of a and b
	 * 
	 * @param a
	 *            Vector
	 * @param b
	 *            Vector
	 * @return a.b
	 */
	public static double dotProd(Vec a, Vec b) {
		return a.dotProd(b);
	}

	/**
	 * Returns a string that contains the values of this vector. The form is
	 * (x,y,z).
	 * 
	 * @return the String representation
	 */
	public String toString() {
		return "(" + this.x + ", " + this.y + ", " + this.z + ")";
	}

	@Override
	public Vec clone() {
		return new Vec(this);
	}
	
	public Color toColor() {
        float r = (float)(x <= 1.0D ? x : 1.0D);
        float g = (float)(y <= 1.0D ? y : 1.0D);
        float b = (float)(z <= 1.0D ? z : 1.0D);
        return new Color(r, g, b);
    }
	
	 public void multiplyElement(Vec a) {
	        x *= a.x;
	        y *= a.y;
	        z *= a.z;
    }
}
